/*!
 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

import DirectedGraph from 'nfvo-utils/DirectedGraph.js';

function findCycles(
    graph,
    node,
    id,
    visited = {},
    visitedConnections = {},
    recursionStack = {},
    connectionsWithCycle = {}
) {
    visited[node] = true;
    recursionStack[node] = true;
    if (id) {
        visitedConnections[id] = true;
    }
    for (let edge of graph.getEdges(node)) {
        if (!visited[edge.target]) {
            findCycles(
                graph,
                edge.target,
                edge.id,
                visited,
                visitedConnections,
                recursionStack,
                connectionsWithCycle
            );
        } else if (recursionStack[edge.target]) {
            visitedConnections[edge.id] = true;
            for (let connection in visitedConnections) {
                connectionsWithCycle[connection] = true;
            }
        }
    }
    recursionStack[node] = false;
    return {
        visitedNodes: visited,
        connectionsWithCycle: connectionsWithCycle
    };
}

export function checkCyclesAndMarkDependencies(dependenciesList) {
    let overallVisitedNodes = {};
    let overallConnectionsWithCycles = {};

    let g = new DirectedGraph();
    for (let dependency of dependenciesList) {
        if (dependency.sourceId !== null && dependency.targetId !== null) {
            g.addEdge(dependency.sourceId, dependency.targetId, {
                id: dependency.id
            });
        }
    }

    for (let node in g.nodes) {
        if (!overallVisitedNodes.node) {
            let { visitedNodes, connectionsWithCycle } = findCycles(
                g,
                node,
                undefined
            );
            overallVisitedNodes = { ...overallVisitedNodes, ...visitedNodes };
            overallConnectionsWithCycles = {
                ...overallConnectionsWithCycles,
                ...connectionsWithCycle
            };
        }
    }
    return dependenciesList.map(dependency => ({
        ...dependency,
        hasCycle:
            dependency.sourceId && dependency.targetId
                ? overallConnectionsWithCycles.hasOwnProperty(dependency.id)
                : undefined
    }));
}
